﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace TK
{
    public partial class CustomSerializer
    {
        class CachedPropInfo {
            internal readonly Dictionary<string, PropInfo> GroupedByName;
            internal readonly Dictionary<string, PropInfo> GroupedByStoredName;

            internal CachedPropInfo(PropInfo[] memberInfos) {
                GroupedByName = memberInfos.ToDictionary(p => p.Name);
                GroupedByStoredName = memberInfos.ToDictionary(p => p.StoreName);
            }
        }

        #region cacheTypes / cacheCreateInstance
        static readonly BindingFlags DEF_BINDING_FLAGS = BindingFlags.Public | BindingFlags.Instance;
        static readonly ConcurrentDictionary<string, Type> cacheTypes = new ConcurrentDictionary<string, Type>();
        static readonly ConcurrentDictionary<Type, ObjectActivator> cacheCreateInstance = new ConcurrentDictionary<Type, ObjectActivator>();
        static readonly ConcurrentDictionary<Type, CachedPropInfo> cacheProps = new ConcurrentDictionary<Type, CachedPropInfo>();
        static readonly ConcurrentDictionary<Type, Dictionary<Type, Attribute>> cacheAttributes = new ConcurrentDictionary<Type, Dictionary<Type, Attribute>>();

        static void updateType(Type t)
        {
            if (t != null) cacheTypes.TryAdd(t.FullName, t);
        }

        static Type getTypeByName(string name, Dictionary<string,Type> classMaps) {
            Type r;
            if (classMaps.TryGetValue(name, out r)) return r;

            r = cacheTypes.GetOrAdd(name, typeResolve);
#if DEBUG
            if (r == null)
            {                
                Trace.TraceWarning("Type.GetType: can't found " + name, "CustomSerializer");
            }
#endif
            return r;
        }

        static Type typeResolve(string typeName)
        {
            var tt = Type.GetType(typeName);
            if (tt != null) return tt;

            return Type.GetType(typeName,
                assemblyName => AssemblyDirectories.Select(p => Path.Combine(p, assemblyName.Name) + ".dll").Where(p => File.Exists(p)).Select(p => Assembly.LoadFrom(p)).FirstOrDefault(),
                (assembly, s, arg3) => assembly.GetType(s));
        }
        
        static object createObject(Type type)
        {
            return type.IsValueType
                ? Activator.CreateInstance(type)
                : cacheCreateInstance.GetOrAdd(type, t => createInstance(type))();
        }

        static Dictionary<string, PropInfo> getTypeProps(Type t, bool groupByStoreName)
        {
            var result = cacheProps.GetOrAdd(t, type =>
            {
                var props = type.GetProperties(DEF_BINDING_FLAGS).
                    Where(p =>
                        !p.GetCustomAttributes(typeof (CSIgnore), true).Any()
                        && ((p.CanWrite && p.CanRead) || (type.Name.StartsWith("Tuple`"))) // Tuple<> - специальный тип
                        && !p.GetIndexParameters().Any()).
                    Select(p => new PropInfo(p)).ToArray();

                var fields = type.GetFields(DEF_BINDING_FLAGS).
                    Where(p => !p.GetCustomAttributes(typeof (CSIgnore), true).Any()).
                    Select(p => new PropInfo(p)).ToArray();

                var r = props.Concat(fields).ToArray();
                try { return new CachedPropInfo(r); }
                catch (ArgumentException e) {
                    throw new CustomSerializerException(SerializerFailType.DuplicateName, t);
                }
            });
            return groupByStoreName ? result.GroupedByStoredName : result.GroupedByName;
        }

        static Dictionary<Type,Attribute> getTypeAttributes(Type t) {
            var result = cacheAttributes.GetOrAdd(
                t,
                type => {
                    return type.GetCustomAttributes(true).
                        OfType<Attribute>().
                        Where(attr => attr is CSOption || attr is CSName || attr is CSDirect).
                        ToDictionary(attr => attr.GetType());
                });
            return result;
        }

        delegate object ObjectActivator();
        static ObjectActivator createInstance(Type type)
        {
            var dynamicMethod = new DynamicMethod("CreateInstance", type, Type.EmptyTypes, true);
            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
            ilGenerator.Emit(OpCodes.Nop);
            ilGenerator.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes));
            ilGenerator.Emit(OpCodes.Ret);
            return (ObjectActivator)dynamicMethod.CreateDelegate(typeof(ObjectActivator));
        }
        #endregion

        static object getDefaultValue(Type type) {
            return type.IsValueType ? createObject(type) : null;
        }
    }    
}